package _class;

import example.Person;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.ElementType;
import java.util.Properties;

/**
 * * Class类的理解：
 * * （以Java类的加载为例）
 * * 针对于编写好的，java源文件进行编译(使用javac.exe)，会生成一个或多个.class字节码文件。
 * * 接着，使用java.exe命令对指定的.class文件进行解释运行。
 * * 这个解释运行过程中，需要将.class字节码文件加载(使用类的加载器)到内存中(存放在 方法区)。
 * * 加载到内存中的.class文件对应的结构即为Class的一个实例。
 * <p>
 * 类的加载过程
 * 1.类的装载(loading)
 * 将类的class文件读入内存，并为之创建一个java.lang.Class对象。
 * 此过程由类加载器完成。
 * 2.链接(linking)
 * -2.1 验证(Verify):确保加载的类信息符合JVM规范，例如，以cafebabe开头，没有安全方面的问题。
 * -2.2 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段，这些内存都将在方法区中进行分配。
 * -2.3 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
 * 3.初始化(Initialization)
 * 执行类构造器<clinit>()方法的过程。
 * 类构造器<clinit>()方法是由编译期自动采集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
 * <p>
 * 类的加载器
 * 1.作用：负责类的加载，并对应于一个Class的实例。
 * 2.分类：2种
 * > BootstrapClassLoader：引导类加载器、启动类加载器
 * ----使用C/C++语言编写，不能通过Java代码获取其实例。
 * ----负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)
 * > 继承于ClassLoader的类加载器
 * --ExtensionClassLoader：扩展类加载器
 * -----> 负责加载从java.ext.dirs系统属性所指定的目录中加载类库，或从JDK的安装目录的jre/lib/ext子目录下加载类库。
 * --SystemClassLoader/ApplicationClassLoader：系统类加载器、应用程序类加载器
 * -----> 自定义的类，默认使用的类的加载器。
 * --用户自定义类的加载器
 * -----> 实现应用的隔离（同一个类在一个应用程序中可以加载多份）
 *
 * <p>
 * 以上类加载器没有继承关系！！
 * 双亲委托机制
 */
public class ClassTest {
    /**
     * 获取Class实例的几种方式
     */
    @Test
    public void test1() throws ClassNotFoundException {
        // 1.调用运行时的静态属性：class
        Class<Person> personClass = Person.class;
        System.out.println(personClass);

        // 2.调用运行时类的对象的getClass()
        Person person = new Person();
        Class<? extends Person> personClass1 = person.getClass();
        System.out.println(personClass1);

        // 运行时类在内存中会缓存起来，在整个执行期间，只会加载一次。
        System.out.println(personClass == personClass1); //true

        // 3. 调用Class的静态方法forName(String className)
        String className = "example.Person";
        Class<?> class3 = Class.forName(className);
        System.out.println(personClass == class3);// true

        // 4.使用类的加载器的方式
        Class<?> loadClass = ClassLoader.getSystemClassLoader().loadClass("example.Person");
        System.out.println(class3 == loadClass); //true
    }

    @Test
    public void test2() {
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class; // class [[I
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = int.class;
        Class c8 = void.class;
        Class c9 = Class.class;


        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass(); //class [I

        // 只要元素类型与纬度一样，就是同一个Class
        System.out.println(c10 == c11); // true

        System.out.println(c4);
        System.out.println(c11);
    }

    /**
     * JDK17中测试
     * 与 8 有差异
     */
    @Test
    public void testClassLoader() throws ClassNotFoundException {
        // jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);
        // jdk.internal.loader.ClassLoaders$PlatformClassLoader@256216b3
        System.out.println(classLoader.getParent());
        // 获取引导类加载器：失败
        System.out.println(classLoader.getParent().getParent()); // null

        Class<Person> personClass = Person.class;
        // jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
        System.out.println(personClass.getClassLoader());

        Class<?> stringClass = Class.forName("java.lang.String");
        // null  对于Java的核心API使用引导类加载器加载
        System.out.println(stringClass.getClassLoader());
    }

    /**
     * 通过ClassLoader加载指定的配置文件
     */
    @Test
    public void test3() {
        Properties properties = new Properties();
        // 通过类的加载器读取的文件的默认路径为：当前module下的src下
        // 相对于当前类加载器的默认位置
        try (InputStream resourceAsStream =
                     ClassLoader.getSystemClassLoader().getResourceAsStream("info.properties")) {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String name = properties.getProperty("name");
        String psd = properties.getProperty("psd");

        System.out.println(name);
        System.out.println(psd);

    }

    /**
     * Properties
     */
    @Test
    public void test4() {
        Properties properties = new Properties();
        try (FileInputStream fis = new FileInputStream("info.properties")) {
            properties.load(fis);
            String name = properties.getProperty("name");
            String psd = properties.getProperty("psd");

            System.out.println(name);
            System.out.println(psd);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}
